package com.yellow.cloud.security.oauth2.service;

import com.alibaba.fastjson.JSON;
import com.yellow.cloud.security.autoconfigure.SecurityProperties;
import com.yellowframework.cloud.common.client.YellowCloudServerList;
import com.yellowframework.cloud.common.exception.ExceptionCast;
import com.yellowframework.cloud.domain.security.AuthToken;
import com.yellowframework.cloud.domain.security.response.AuthCode;
import com.yellowframework.cloud.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 认证授权业务层
 * @Author zhou
 * @Date 2020/9/24 13:28
 */
@Service
public class AuthService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuthService.class);

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancerClient loadBalancerClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public static final ThreadLocal<String> USERNAME = ThreadLocal.withInitial(String::new);

    /**
     * 登录
     *
     * @param username
     * @param password
     * @return AuthToken
     * @author zhouhao
     * @date 2020/9/24 13:29
     */
    public AuthToken login(String username, String password, String clientId, String clientSecret) {

        // 申请令牌
        AuthToken authToken = applyToken(username, password, clientId, clientSecret);

        // 将 token存储到redis
        String access_token = authToken.getAccess_token();
        String content = JSON.toJSONString(authToken);
        boolean saveTokenResult = saveToken(access_token, content, SecurityProperties.auth.getUserValiditySeconds());
        if (!saveTokenResult) {
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        USERNAME.set(username);
        return authToken;
    }

    /**
     * 申请令牌
     *
     * @param username 用户名
     * @param password 密码
     * @param clientId 客户端id
     * @param clientSecret 客户端密钥
     * @return AuthToken
     * @author zhouhao
     * @date 2020/9/24 13:32
     */
    private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {

        // 选中认证服务的地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(YellowCloudServerList.YELLOW_CLOUD_SECURITY);
        if (serviceInstance == null) {
            LOGGER.error("choose an auth instance fail");
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_AUTHSERVER_NOTFOUND);
        }

        // 获取令牌的url
        String path = serviceInstance.getUri().toString() + "/oauth/token";

        // 定义body
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        // 授权方式
        formData.add("grant_type", "password");
        // 账号
        formData.add("username", username);
        // 密码
        formData.add("password", password);

        // 定义头
        MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
        header.add("Authorization", getHttpBasic(clientId, clientSecret));

        //指定 restTemplate当遇到400或401响应时候也不要抛出异常，也要正常返回值
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {

                //当响应的值为400或401时候也要正常响应，不要抛出异常
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });

        Map map = null;

        try {

            // http请求spring security的申请令牌接口
            ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(formData, header), Map.class);

            map = mapResponseEntity.getBody();
        } catch (RestClientException e) {
            e.printStackTrace();
            LOGGER.error("request oauth_token_password error: {}", e.getMessage());
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }

        // jti是jwt令牌的唯一标识作为用户身份令牌
        if (map == null || map.get("access_token") == null || map.get("refresh_token") == null || map.get("jti") == null) {
            ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
        }

        AuthToken authToken = new AuthToken();
        //访问令牌(jwt)
        authToken.setJwt_token(StringUtils.valueOf(map.get("access_token")));
        //jti，作为用户的身份标识
        authToken.setAccess_token(StringUtils.valueOf(map.get("jti")));
        //刷新令牌(jwt)
        authToken.setRefresh_token(StringUtils.valueOf(map.get("refresh_token")));

        return authToken;
    }

    /**
     * 获取httpbasic的串
     *
     * @param clientId     客户端id
     * @param clientSecret 客户端密钥
     * @return java.lang.String
     * @author zhouhao
     * @date 2020/9/24 13:38
     */
    private String getHttpBasic(String clientId, String clientSecret) {

        String string = clientId + ":" + clientSecret;

        //将串进行base64编码
        byte[] encode = Base64Utils.encode(string.getBytes());

        return "Basic " + new String(encode);
    }

    /**
     * 存储令牌到redis
     *
     * @param access_token 用户身份令牌
     * @param content 存储的内容（AuthToken的内容）
     * @param ttl 令牌过期时间
     * @return boolean
     * @author zhouhao
     * @date 2020/9/24 13:31
     */
    private boolean saveToken(String access_token, String content, long ttl) {

        // 令牌名称
        String name = "user_token:" + access_token;

        // 保存到令牌到redis
        stringRedisTemplate.boundValueOps(name).set(content, ttl, TimeUnit.SECONDS);

        // 获取过期时间
        Long expire = stringRedisTemplate.getExpire(name);

        return expire > 0;
    }

    /**
     * 删除用户redis中的token
     *
     * @param access_token 用户身份令牌
     * @return
     * @author zhouhao
     * @date 2020/9/24 13:31
     */
    public void delToken(String access_token) {

        // 令牌名称
        String name = "user_token:" + access_token;

        // 删除token
        stringRedisTemplate.delete(name);
    }

    /**
     * 从redis查询用户令牌
     * @author Hao.
     * @date 2020/10/21 18:50
     * @param token 身份令牌
     * @return AuthToken
     */
    public AuthToken getUserToken(String token){
        String userToken = "user_token:" + token;
        String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
        AuthToken authToken;
        try {
            authToken = JSON.parseObject(userTokenString, AuthToken.class);
        } catch (Exception e) {
            LOGGER.error("getUserToken from redis and execute JSON.parseObject error {}",e.getMessage());
            e.printStackTrace();
            return null;
        }
        return authToken;
    }
}

